package com.bianmaba.spring.data.jpa.domain.specification;

import com.bianmaba.spring.data.jpa.domain.specification.condition.param.Param;
import com.bianmaba.spring.data.jpa.domain.specification.condition.predicate.PredicateGeneratorFactory;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.query.criteria.internal.path.PluralAttributePath;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.query.QueryUtils;

import javax.persistence.criteria.*;
import java.util.Map;
import java.util.Set;

/**
 * Created by cwx183898 on 2017/8/18.
 */
public class DynamicSpecification<T> implements Specification<T> {
    private Pageable pageable;
    private Map<String, Object> params;
    private boolean distinct = false;

    public DynamicSpecification(Map<String, Object> params, Pageable pageable) {
        this.params = params;
        this.pageable = pageable;
    }

    public DynamicSpecification(Map<String, Object> params, Pageable pageable, boolean distinct) {
        this.params = params;
        this.pageable = pageable;
        this.distinct = distinct;
    }

    @Override
    public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
        if (pageable != null) {
            if (pageable.getSort() != null) {
                query.orderBy(QueryUtils.toOrders(pageable.getSort(), root, criteriaBuilder));
            }
        }
        query.distinct(this.distinct);
        Predicate predicate = null;
        if (params != null) {
            Set<Map.Entry<String, Object>> entries = params.entrySet();
            for (Map.Entry<String, Object> entry : entries) {
                predicate = createPredicate(root, criteriaBuilder, predicate, entry);
            }
        }
        return predicate;
    }

    private Predicate createPredicate(Root<T> root, CriteriaBuilder criteriaBuilder, Predicate predicate, Map.Entry<String, Object> entry) {
        Param param = new Param(entry);
        Path path = getPath(root, root, param.getPath());
        Predicate newPredicate = PredicateGeneratorFactory.getGenerator(param.getOper()).toPredicate(criteriaBuilder, path, param);
        if (predicate != null) {
            if (param.getJoin().equals("or")) {
                predicate = criteriaBuilder.or(predicate, newPredicate);
            } else {
                predicate = criteriaBuilder.and(predicate, newPredicate);
            }
        } else {
            predicate = newPredicate;
        }
        return predicate;
    }


    /**
     * 根据属性路径生找到整路径
     *
     * @param root
     * @param path
     * @param propertyPath
     * @param <X>
     * @return
     */
    private <X> Path<X> getPath(Root<T> root, Path<X> path, String propertyPath) {
        if (path == null || StringUtils.isEmpty(propertyPath)) {
            return path;
        }
        String property = StringUtils.substringBefore(propertyPath, ".");
        Path next = null;
        if (PluralAttributePath.class.isAssignableFrom(path.getClass())) {
            PluralAttributePath pap = (PluralAttributePath) path;
            Join join = root.join(pap.getAttribute().getName(), JoinType.LEFT);
            next = join.get(property);
        } else {
            next = path.get(property);
        }
        return getPath(root, next, StringUtils.substringAfter(propertyPath, "."));
    }

}